package com.skp.shaphan.actions;

import com.skp.shaphan.model.ConnectionDetails;
import com.skp.shaphan.ui.SQLEditor;

/**
 * Formats a piece of SQL from a SQLEditor
 * @author Stephen Phillips
 *
 */
public class FormatSQLAction extends Thread implements Runnable {
	private SQLEditor editor;
	private StringBuilder sb;
	private final int UNKNOWN_LOCATION = 1;
	private final int SELECT_LOCATION = 2;
	private final int FROM_LOCATION = 3;
	private final int WHERE_LOCATION = 4;
	private final int INDENT_SIZE = 3;
	private int clauseLocation;
	private StringBuilder token;
	private int indent;
	private int lineLength;
	private boolean needsWhitespace;
	private int parenLevel;
	private int selectIndent;
	private boolean inQuotes;
	
	
	public FormatSQLAction() {
	}
	
	@Override
	public void run() {
	
		// What SQL do we format
		ConnectionDetails cd = editor.getConnection();
		String sqlStatement = editor.getSQL();
		
		// Remove all of the carriage returns
		sqlStatement = sqlStatement.replaceAll("\n", "");
		
		sb = new StringBuilder();
		token = new StringBuilder();
		clauseLocation = UNKNOWN_LOCATION;
		indent = 0;
		lineLength = 0;
		needsWhitespace = true;
		parenLevel = 0;
		char c;
		boolean keywordChar;
		boolean whitespace;
		for(int x=0; x < sqlStatement.length(); x++) {
			keywordChar = false;
			c = sqlStatement.charAt(x);
			if(c == '_') keywordChar = true;
			if(c >= 'a' && c <= 'z') keywordChar = true;
			if(c >= 'A' && c <= 'Z') keywordChar = true;
			if(c >= '0' && c <= '9') keywordChar = true;
			
			if(c == '\'') {
				if(inQuotes) {
					needsWhitespace = false;
				}
				inQuotes = !inQuotes;
			}
			
			if(c == '(') {
				indent+= INDENT_SIZE;
				needsWhitespace = false;
				parenLevel++;
			}
			if(c == ')') {
				indent-= INDENT_SIZE;
				parenLevel--;
			}
			whitespace = false;
			if(c == ' ') {
				whitespace = true;
			}
			if(c == '\t') {
				whitespace = true;
			}
			
			if(inQuotes && c != '\'') {
				sb.append(c);
				lineLength++;
			} else {
				if(keywordChar) {
					token.append(c);
					lineLength ++;
				} else {
					if(token.length() > 0) {
						if(lineLength == 0) {
							needsWhitespace = false;
						}
						processToken();
						if(needsWhitespace) {
							sb.append(" ");
							lineLength++;
						}
						sb.append(token);
						token.setLength(0);
						needsWhitespace = true;
					}
					if(!whitespace) {
						sb.append(c);
						lineLength++;
					}
				}
			}
			
			// handle commas in the Select clause
			if(c == ',' && clauseLocation == SELECT_LOCATION  && parenLevel == 0) {
				newLine();
			}
			
			// handle period for aliases -- no whitespace after
			if(c == '.') {
				needsWhitespace = false;
			}
			
			if(whitespace) {
				needsWhitespace = true;
			}
		}
		
		if(token.length() > 0) {
			sb.append(token);
		}
		
		// update the SQL
		editor.setSQL(sb.toString());
	}
	
	private void processToken() {
		if(token.toString().equalsIgnoreCase("SELECT")) {
			clauseLocation = SELECT_LOCATION;
			selectIndent = token.length() + 1;
			newLine();
		}
		if(clauseLocation == SELECT_LOCATION && token.toString().equalsIgnoreCase("DISTINCT")) {
			selectIndent += token.length() + 1;
		}
		if(token.toString().equalsIgnoreCase("FROM")) {
			clauseLocation = FROM_LOCATION;
			newLine();
		}
		if(token.toString().equalsIgnoreCase("WHERE")) {
			clauseLocation = WHERE_LOCATION;
			newLine();
		}		
		if(token.toString().equalsIgnoreCase("AND")) {
			newLine();
		}		
		if(clauseLocation == FROM_LOCATION) {
			if(token.toString().equalsIgnoreCase("ON")) {
				newLine();
			}		
			if(token.toString().equalsIgnoreCase("LEFT")) {
				newLine();
			}		
		}
	}
	
	private void newLine() {
		sb.append('\n');
		lineLength = 0;
		int indent = this.indent;
		if(clauseLocation == SELECT_LOCATION && !token.toString().equalsIgnoreCase("SELECT")) {
			indent += selectIndent;
		}
		for(int x=0; x<indent; x++) {
			sb.append(" ");
		}
		needsWhitespace = false;
	}

	public SQLEditor getEditor() {
		return editor;
	}

	public void setEditor(SQLEditor editor) {
		this.editor = editor;
	}


}
